﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Text.RegularExpressions;

namespace SiteParser.Core
{
	public class RegexpHtmlIterator : IHtmlIterator
	{
		protected const string BRIEF_CLOSE_TAG = "/>";
		protected const string START_TAG_REGEX = "<\\s*(([^\\s>])+)";
		protected const string START_TAG_TEMPLATE = "<\\s*{0}";
		protected const string END_TAG_TEMPLATE = "<\\s*/{0}\\s*>";
		protected const string ATTRIBUTE_REGEX_TEMPLATE = "{0}\\s*=\\s*[\"']([^\"']*)[\"']";

		protected readonly HtmlNavigationExpression expression;
		protected readonly IExpressionBuilder expressionBuilder;
		protected Regex regexHtmlExpression;
		protected Match currentMatch;
		protected string htmlContent;

		private bool compiled = false;

		#region Constructors

		/*internal RegexpHtmlIterator(TextReader navigatorReader, HtmlNavigationExpression expression, IExpressionBuilder expressionBuilder) :
			this(navigatorReader.ReadToEnd(), expression, expressionBuilder) {
		}*/

		internal RegexpHtmlIterator(string htmlContent, HtmlNavigationExpression expression, IExpressionBuilder expressionBuilder) {
			if (htmlContent == null) {
				throw new ArgumentNullException("htmlContent");
			}
			if (expression == null) {
				throw new ArgumentNullException("expression");
			}
			if (expressionBuilder == null) {
				throw new ArgumentNullException("expressionBuilder");
			}

			this.htmlContent = htmlContent;
			this.expression = expression;
			this.expressionBuilder = expressionBuilder;
		}

		#endregion Constructors

		public void Compile() {
			regexHtmlExpression = new Regex(expressionBuilder.BuildExpression(expression),
				RegexOptions.Singleline | RegexOptions.Compiled | RegexOptions.IgnoreCase);

			compiled = true;
		}

		#region IHtmlIterator Members

		public virtual bool MoveNext() {
			if (!compiled) {
				Compile();
			}

			if (currentMatch == null) {
				currentMatch = regexHtmlExpression.Match(htmlContent);
			}
			else {
				currentMatch = currentMatch.NextMatch();
			}

			return Success;
		}

		public virtual string SelectedValue {
			get {
				switch (expression.SelectType) {
					case SelectTypes.InnerHtml:
						return GetCurrentMatchInnerHtml();
					case SelectTypes.AttributeValue:
						return GetCurrentMatchAttributeValue(expression.SelectingAttributeName);
					case SelectTypes.Constant:
						return expression.SelectConstantValue;
					case SelectTypes.OriginalHtml:
						return htmlContent;
					case SelectTypes.Regex:
						return GetRegexSelectingValue();
				}
				return GetCurrentMatchInnerHtml(); 
			}
		}

		public virtual bool Success {
			get { return (currentMatch != null) && (currentMatch.Success || expression.SelectType == SelectTypes.Constant); }
		}

		public virtual string GetAttributeValue(string attributeName) {
			if (string.IsNullOrEmpty(attributeName)) {
				throw new ArgumentNullException("attributeName");
			}

			return GetCurrentMatchAttributeValue(attributeName);
		}

		#endregion

		#region Protected methods

		protected string GetCurrentMatchAttributeValue(string attributeName) {
			string attributePattern = string.Format(ATTRIBUTE_REGEX_TEMPLATE, attributeName);
			Match attributeMatch = Regex.Match(currentMatch.Value, attributePattern);

			if (attributeMatch.Success) {
				return attributeMatch.Groups[1].Value;
			}

			return string.Empty;
		}

		protected string GetCurrentMatchInnerHtml() {
			if (currentMatch != null) {
				//For cases like "<div id='id_1' />
				if (!currentMatch.Value.Contains(BRIEF_CLOSE_TAG)) {
					Match startTagMatch = Regex.Match(currentMatch.Value, START_TAG_REGEX);
					if (startTagMatch.Success) {
						string tag = startTagMatch.Groups[1].Value;
						string startTagRegexPattern = string.Format(START_TAG_TEMPLATE, tag);
						string endTagRegexPattern = string.Format(END_TAG_TEMPLATE, tag);
						string startOrEndTagRegexPattern = string.Format("({0})|({1})", startTagRegexPattern, endTagRegexPattern);

						Regex startTagRegex = new Regex(startTagRegexPattern, RegexOptions.Compiled);
						Regex startOrEndTagRegex = new Regex(startOrEndTagRegexPattern, RegexOptions.Compiled);

						string searchContext = htmlContent.Substring(currentMatch.Index + currentMatch.Value.Length);

						Match startOrEndTagMatch = startOrEndTagRegex.Match(searchContext);

						int inlineCount = 0;
						int endPosition = 0;
						while (startOrEndTagMatch.Success) {
							inlineCount = startTagRegex.IsMatch(startOrEndTagMatch.Value) ? inlineCount + 1 : inlineCount - 1;

							if (inlineCount == -1) {
								endPosition = startOrEndTagMatch.Index;
								break;
							}

							startOrEndTagMatch = startOrEndTagMatch.NextMatch();
						}
						if (inlineCount != -1 && endPosition == 0) {
							endPosition = searchContext.Length;
						}

						return searchContext.Substring(0, endPosition);
					}
				}
			}

			return string.Empty;
		}

		protected string GetRegexSelectingValue() {
			return Regex.IsMatch(htmlContent, expression.RegexValue, RegexOptions.Singleline) ?
				Regex.Match(htmlContent, expression.RegexValue, RegexOptions.Singleline).Groups[1].Value : string.Empty;
		}

		#endregion Protected methods
	}
}
